#pragma comment(linker, "/subsystem:windows")

#include "glew.h"
#include "textureManager.h"
#include "hud.h"
#include "windowsOverlay.h"  
#include "timer.h"
#include "helper.h"
#include "writer.h"					
#include "frame.h"				
#include "fppCamera.h"	
#include "meshManager.h"
#include "logger.h"
#include <string.h>
#include <GLTools.h>
#include <tools.h>
#include <iostream>
#include <freeglut.h>
#include <matrix44.h>

//========================================== TYPEDEF'S ======================================================
typedef Vect<float> V3; //3d vektor


//=========================================== ZMIENNE GLOBALNE ==============================================
// kamera
FPPCamera freelookC;

// predkosc kamery
float freelookCSpeed = 3.0f;

//czas pomiedzy ramkami
float deltaTime = 0;

typedef unsigned int uint;
Timer T;

Frame groundF;
Frame planeF;
Frame sphereF;
Frame teapotF;
Frame torusF;

GLuint shader;

// lights
GLuint shaderPositionLocation;
GLuint shaderColorLocation;
GLuint shaderAngleLocation;
GLuint shaderAttenuationLocation;
GLfloat position[]		= {.0f, 5.0f, .0f};
GLfloat color[]			= {1.0f, 1.0f, 1.0f};
GLfloat	attenuation[]	= {0.0f, 0.3f, .0f}; // linear attenuation
GLfloat	angle			= 180.0f;

// material
GLuint shaderAmbientColorLocation;
GLuint shaderDiffuseColorLocation;
GLuint shaderSpecularColorLocation;
GLuint shaderSpecularExponentLocation;
GLfloat ambientColor[] = {1.0f, 1.0f, 1.0f};
GLfloat diffuseColor[] = {1.0f, 1.0f, 1.0f};
GLfloat specularColor[] = {1.0f, 1.0f, 1.0f};
GLint   exponent = 100;

// ambient light
GLuint shaderAmbientLightLocation;
GLfloat ambientLight[] = {0.2f, .2f, .2f};

//================================================ FUNKCJE =====================================================
//------------------------------------------------ EmergencyExit ----------------------------------------------
void EmergencyExit( const char* str )
{
	MessageBox(NULL, str, NULL, MB_OK);
	exit(0);
}
//------------------------------------------------ DrawCoordinateSystem ----------------------------------------
void DrawCoordSystem( int s=1 )
{
	glBegin( GL_LINES );
	
		//x
		glColor3f( 1,0,0 );
		glVertex3f( 0.0f, 0.0f, 0.0f );
		glVertex3f( 1.0f*s, 0.0f, 0.0f );
		  
		//y
		glColor3f( 0,1,0);
		glVertex3f( 0.0f, 0.0f, 0.0f );
		glVertex3f( 0.0f, 1.0f*s, 0.0f );

		//z
		glColor3f( 0,0,1 );
		glVertex3f( 0.0f, 0.0f, 0.0f );
		glVertex3f( 0.0f, 0.0f, 1.0f*s );

	glEnd();

	glColor3f( 1.0, 1.0, 1.0 );
}
//------------------------------------------------ DrawGround --------------------------------------------------
void DrawGround(float size, float division)
{
	float gap = size/division;

	glNormal3f(0.0f, 1.0f, 0.0f);
	for (float i = 0.0f; i < size; i += gap)
	{
		for (float j = 0.0f; j < size; j += gap)
		{
			glBegin(GL_QUADS);
				glVertex3f(i, 0.0f, j);
				glVertex3f(i + gap, 0.0f, j);
				glVertex3f(i + gap, 0.0f, j + gap);
				glVertex3f(i, 0.0f, j + gap);
			glEnd();
		}
	}
}
//------------------------------------------------ DrawGround --------------------------------------------------
void DrawGround( int s, const char* texName )
{
	glEnable( GL_TEXTURE_2D );
	TextureManagerAPI->ActivateTexture( texName );
	glBegin( GL_QUADS );

		glTexCoord2f( 0.0f, 0.0f );
		glVertex3f( 0.0f, 0.0f, 0.0f );
		
		glTexCoord2f( 1.0f, 0.0f );
		glVertex3f( 1.0f*s, 0.0f, 0.0f );

		glTexCoord2f( 1.0f, 1.0f );
		glVertex3f( 1.0f*s, 0.0f, 1.0f*s );

		glTexCoord2f( 0.0f, 1.0f );
		glVertex3f( 0.0f, 0.0f, 1.0f*s );

	glEnd();
	glDisable( GL_TEXTURE_2D );
}
//------------------------------------------------ DrawTexturedQuad --------------------------------------------
void DrawTexturedQuad()
{
	glBegin(GL_QUADS);
		glTexCoord2f( 0.0, 0.0 );
		glVertex3f( 0.0, 0.0, 0.0 );

		glTexCoord2f( 1.0, 0.0 ); 
		glVertex3f( 1.0, 0.0, 0.0 );

		glTexCoord2f( 1.0, 1.0 );
		glVertex3f( 1.0, 1.0, 0.0 );

		glTexCoord2f( 0.0, 1.0 );
		glVertex3f( 0.0, 1.0, 0.0 );
	glEnd();
}
//------------------------------------------------ DrawQuad ----------------------------------------------------
void DrawQuad()
{
	glBegin(GL_QUADS);
		glTexCoord2f( 0.0, 0.0 );

		glTexCoord2f( 1.0, 0.0 ); 

		glTexCoord2f( 1.0, 1.0 );

		glTexCoord2f( 0.0, 1.0 );
	glEnd();
}
//------------------------------------------------ ShowFPS -----------------------------------------------------
float FPSCounter()
{
    static Timer timer;
    static int count = 0;
    float elapsedTime;
	static float FPS = 0.0f;

    // update fps every second
    elapsedTime = (float)timer.getElapsedTime();
    if(elapsedTime < 1.0)
    {
        ++count;
    }
	else
	{
		FPS = ( (float)count / (float)elapsedTime );
		
		count = 0;
		timer.start();
	}

	return FPS;
}
//------------------------------------------------- cameraControls --------------------------------------------
void cameraControls()
{
	static float angle=0.0f;

	if( WindowsOverlayAPI->IsKeyDown( VK_LEFT ) )
	{
		freelookC.RotateLocalX( -angle );
		freelookC.RotateLocalY( freelookCSpeed * deltaTime );
		freelookC.RotateLocalX( angle );
	}

	if( WindowsOverlayAPI->IsKeyDown( VK_RIGHT ) )
	{
		freelookC.RotateLocalX( -angle );
		freelookC.RotateLocalY( -freelookCSpeed * deltaTime );
		freelookC.RotateLocalX( angle );
	}

	if( WindowsOverlayAPI->IsKeyDown( VK_DOWN ) )
	{
		freelookC.RotateLocalX( freelookCSpeed * deltaTime );
		angle += freelookCSpeed * deltaTime;
	}

	if( WindowsOverlayAPI->IsKeyDown( VK_UP ) )
	{
		freelookC.RotateLocalX( -freelookCSpeed * deltaTime );
		angle += (-freelookCSpeed * deltaTime);
	}

	//move forward
	if( WindowsOverlayAPI->IsKeyDown( 'W' ) )
		freelookC.TranslateAlongZ( freelookCSpeed * 3 * deltaTime );

	//move backward
	if( WindowsOverlayAPI->IsKeyDown( 'S' ) )
		freelookC.TranslateAlongZ( -freelookCSpeed*3 * deltaTime );

	//strafe left
	if( WindowsOverlayAPI->IsKeyDown( 'A' ) )
		freelookC.TranslateAlongX( freelookCSpeed * 3 * deltaTime );

	//strafe right
	if( WindowsOverlayAPI->IsKeyDown( 'D' ) )
		freelookC.TranslateAlongX( -freelookCSpeed * 3 * deltaTime );
}
//------------------------------------------------- MouseFunc --------------------------------------------------
bool MouseFunc()
{
	cameraControls();

	return true;
}
//---------------------------------------------- KeybordFunc ---------------------------------------------------
bool KeybordFunc()
{
	return true;
}
//------------------------------------------------ DrawScene ---------------------------------------------------
void DrawScene()
{
	/*
		W tej funkcji budujemy cala scene.
	*/
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glUseProgram(shader);
	float r = 1.0f;

	glPushMatrix();
	freelookC.ApplyCameraTransform();
	SendDataToGPU(freelookC.GetMatrix(false));
	DrawLightPosition();

		glPushMatrix();
		groundF.ApplyActorTransform();
			DrawCoordSystem();
			DrawGround(20.0f, 100.0f);
		glPopMatrix();

		glPushMatrix();
		planeF.RotateLocalZ(r * deltaTime);
		planeF.ApplyActorTransform();
			MeshManagerAPI->DrawModel("toyplane");
		glPopMatrix();

		glPushMatrix();
		sphereF.ApplyActorTransform();
			glutSolidSphere(1, 100, 100);
		glPopMatrix();

		glPushMatrix();
		teapotF.RotateLocalY(r * deltaTime);
		teapotF.RotateLocalX(r * deltaTime);
		teapotF.ApplyActorTransform();
			glutSolidDodecahedron();
		glPopMatrix();

		glPushMatrix();
		torusF.RotateLocalY(r * deltaTime);
		torusF.RotateLocalX(r * deltaTime);
		torusF.ApplyActorTransform();
			glutSolidTorus(0.5, 1.0, 100, 100);
		glPopMatrix();

	glPopMatrix();

	glFlush();
}
//------------------------------------------------ RenderFunc --------------------------------------------------
void RenderFunc()
{
	deltaTime = (float)T.getElapsedTime();
	T.start();

	DrawScene();

	//wydruki na ekran
	glColor3f( 1.0, 1.0, 1.0 );
	HudAPI->PutCanvas( 100, 100 );
		WriterAPI->Print( 0, 0, "FPS: %f", FPSCounter() );
	HudAPI->TakeCanvas();

	glFlush();
	cout << EngineTools::GetOpenglError();
}
//------------------------------------------------ ResizeFunc -------------------------------------------------
void ResizeFunc( int width, int height )
{
	/*      Don't want a divide by 0*/
	if (height == 0)
		height = 1;

	/*      Reset the viewport to new dimensions*/
	glViewport(0, 0, width, height);

	/*      Set current Matrix to projection*/
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity(); //reset projection matrix

	/*      Time to calculate aspect ratio of our window.	*/
	gluPerspective(54.0f, (GLfloat)width/(GLfloat)height, 0.01, 1000.0f);

	glMatrixMode(GL_MODELVIEW); //set modelview matrix
	glLoadIdentity(); //reset modelview matrix
}
//------------------------------------------------ InitWindow ------------------------------------------------
void InitWindow()
{
	WindowsOverlayAPI->InitWindowPosition( 0, 0 );

	WindowsOverlayAPI->InitWindowSize( 800, 600 );

	WindowsOverlayAPI->InitDisplayMode( DM_RGBA | DM_DEPTH );

	WindowsOverlayAPI->SetResizeFunc( ResizeFunc );

	WindowsOverlayAPI->SetRenderFunc( RenderFunc );

	WindowsOverlayAPI->SetKeybordFunc( KeybordFunc );

	WindowsOverlayAPI->SetMouseFunc( MouseFunc );
}

//------------------------------------------------ Init --------------------------------------------------------
void Init()
{

	//---------------------------------------- stan OGL'a -----------------------------------//
	glClearColor(0.0f, 0.0f, 0.50f, 1.0f ); //niebieskawy kolor tla
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
	glLineWidth( 2.0 );

	//wazne! inicjujemy glew
	if (glewInit() != GLEW_OK)
		assert(false && "glewInit different from GLEW_OK");
	if( !glewIsSupported( "GL_VERSION_2_0" ) )
		EmergencyExit("opengl 2.0 not supp");

	//--------------------------------------- inne ------------------------------------//
	WriterAPI->AddNewFont("Aerial Black", 40 );

	//--------------------------------------- shadery --------------------------------//
	shader = gltLoadShaderPairWithAttributes("s.vp", "s.fp", 0);
	if (!glIsProgram(shader))
	{
		assert(false && "glIsProgram");
	}
	GetPositionsFromShader();

	
	//--------------------------------------- ladujemy mesh ---------------------------//
	if( !MeshManagerAPI->LoadModel( "resources\\toyplane.obj", "toyplane" ) )
	{
		assert(false);
	}
	if( !(MeshManagerAPI->GetResource("toyplane")) )
	{
		assert(false);
	}


	//---------------------------------------- rozmieszczamy obiekty na scenie --------//	
	freelookC.Enable();
	freelookC.SetForwardVector(0.0f, 0.0f, 1.0f);
	freelookC.SetUpVector(0.0f, 1.0f, 0.0f);
	freelookC.SetOrigin( 5.0f, 0.0f, 5.0f );

	groundF.SetOrigin( -10.0f, -2.0f, -10.0f );
	groundF.SetForwardVector( 0.0f, 0.0f, 1.0f );
	groundF.SetUpVector( 0.0f, 1.0f, 0.0f );

	planeF.SetOrigin( -3.0f, 0.0f, -3.0f );
	planeF.SetForwardVector( 0.0f, 0.0f, 1.0f );
	planeF.SetUpVector( 0.0f, 1.0f, 0.0f );

	teapotF.SetOrigin( 5.0f, 0.0f, 2.0f );
	teapotF.SetForwardVector( 0.0f, 0.0f, 1.0f );
	teapotF.SetUpVector( 0.0f, 1.0f, 0.0f );

	sphereF.SetOrigin( 3.0f, 0.0f, -2.0f );
	sphereF.SetForwardVector( 0.0f, 0.0f, 1.0f );
	sphereF.SetUpVector( 0.0f, 1.0f, 0.0f );

	torusF.SetOrigin( -3.0f, 0.0f, 2.0f );
	torusF.SetForwardVector( 0.0f, 0.0f, 1.0f );
	torusF.SetUpVector( 0.0f, 1.0f, 0.0f );
}

//------------------------------------------------ CleanUp -----------------------------------------------------
void CleanUp()
{
	WriterAPI->DeleteFont();
}


//================================================ PUNKT STARTOWY ==========================================
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	EngineTools::RedirectIOToConsole();
	InitWindow();
	WindowsOverlayAPI->EstablishWindow( "OpenGL framework" );

	Init();
	
	WindowsOverlayAPI->MainLoop( );

	CleanUp();
	return 0;
}

